package org.jflame.context.spring.converter;

import org.jflame.commons.util.ArrayHelper;
import org.jflame.commons.util.DateHelper;
import org.jflame.commons.util.StringHelper;

import java.text.ParseException;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;

import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.format.Formatter;
import org.springframework.format.FormatterRegistry;

/**
 * 日期类型spring转换器. 支持java.util.Date及子类(如:java.sql.Date,java.sql.Time,java.sql.Timestamp).
 * <ol>
 * <li>支持上下文时区({从{@link LocaleContextHolder} 获取)与系统默认时区的时间换算,只对java.util.date和java.sql.Timestamp类型生效
 * <li>解析时间,支持设置多兼容格式{@link #fallbackPatterns(String[])}
 * </ol>
 * 默认格式: <br>
 * datetime: {@link DateHelper#YYYY_MM_DD_HH_mm_ss}<br>
 * date: {@link DateHelper#YYYY_MM_DD}<br>
 * time: {@link DateHelper#HH_mm_ss}
 * 
 * @author charles.zhang
 */
public class MyDateFormatter<T extends Date> implements Formatter<T> {

    private final Class<T> dateClass;
    private String pattern;// 主格式
    private String[] fallbackPatterns;// 解析时兼容格式
    private TimeZone defaultTimeZone;
    private boolean enableContextTimeZone;

    public MyDateFormatter(Class<T> targetClass) {
        this(targetClass, null);
    }

    public MyDateFormatter(Class<T> targetClass, String datePattern) {
        this(targetClass, datePattern, null, false);
    }

    public MyDateFormatter(Class<T> targetClass, String datePattern, boolean enableContextTimeZone) {
        this(targetClass, datePattern, null, enableContextTimeZone);
    }

    /**
     * 构造函数
     * 
     * @param targetClass 具体时间类型
     * @param datePattern 时间格式
     * @param defaultTimeZone 默认时区,传null使用系统时区
     * @param enableContextTimeZone 是否启用上下文时区与系统时区转换功能
     */
    @SuppressWarnings("unchecked")
    public MyDateFormatter(Class<T> targetClass, String datePattern, TimeZone defaultTimeZone,
            boolean enableContextTimeZone) {
        this.dateClass = targetClass == null ? (Class<T>) Date.class : targetClass;
        this.defaultTimeZone = defaultTimeZone == null ? TimeZone.getDefault() : defaultTimeZone;
        if (StringHelper.isEmpty(datePattern)) {
            if (dateClass == java.sql.Date.class) {
                pattern = DateHelper.YYYY_MM_DD;
            } else if (dateClass == java.sql.Time.class) {
                pattern = DateHelper.HH_mm_ss;
            } else {
                pattern = DateHelper.YYYY_MM_DD_HH_mm_ss;
            }
        } else {
            pattern = datePattern;
        }
        this.enableContextTimeZone = enableContextTimeZone;
    }

    @Override
    public String print(T source, Locale locale) {
        TimeZone tz = enableContextTimeZone ? LocaleContextHolder.getTimeZone() : defaultTimeZone;
        return DateFormatUtils.format(source, pattern, tz, locale);
    }

    @Override
    public T parse(String text, Locale locale) throws ParseException {
        Date val = null;
        // datetime解析时,判断上下文时区与默认时区时否相同, 如果不相同换算到默认时区时间. 只有日期
        if (enableContextTimeZone && (dateClass == Date.class || dateClass == java.sql.Timestamp.class)) {
            TimeZone cxTimeZone = LocaleContextHolder.getTimeZone();

            if (cxTimeZone.getRawOffset() != defaultTimeZone.getRawOffset()) {
                // 先用带时区的ZonedDateTime解析,再转成date
                ZonedDateTime tmpDateTime;
                ZoneId contextZoneId = cxTimeZone.toZoneId();
                try {
                    DateTimeFormatter dft = DateTimeFormatter.ofPattern(pattern, locale)
                            .withZone(contextZoneId);
                    tmpDateTime = ZonedDateTime.parse(text, dft);
                } catch (DateTimeParseException e) {
                    if (ArrayHelper.isNotEmpty(fallbackPatterns)) {
                        for (String p : fallbackPatterns) {
                            try {
                                DateTimeFormatter dft = DateTimeFormatter.ofPattern(p, locale)
                                        .withZone(contextZoneId);
                                tmpDateTime = ZonedDateTime.parse(text, dft);
                            } catch (DateTimeParseException e1) {

                            }
                        }
                    }
                    throw e;
                }

                val = DateHelper.from(tmpDateTime.withZoneSameInstant(defaultTimeZone.toZoneId()));
                return convertTargetType(val);
            }
        }

        try {
            val = DateUtils.parseDate(text, pattern);
            return convertTargetType(val);
        } catch (ParseException e) {
            if (ArrayHelper.isNotEmpty(fallbackPatterns)) {
                for (String p : fallbackPatterns) {
                    try {
                        val = DateUtils.parseDate(text, p);
                        return convertTargetType(val);
                    } catch (ParseException e1) {

                    }
                }
            }
            throw e;
        }

    }

    @SuppressWarnings({ "unchecked" })
    private T convertTargetType(Date val) {
        if (dateClass == java.util.Date.class) {
            return (T) val;
        } else if (dateClass == java.sql.Date.class) {
            return (T) new java.sql.Date(val.getTime());
        } else if (dateClass == java.sql.Time.class) {
            return (T) new java.sql.Time(val.getTime());
        } else if (dateClass == java.sql.Timestamp.class) {
            return (T) new java.sql.Timestamp(val.getTime());
        } else {
            throw new IllegalStateException("unsupported " + dateClass.getName());// 只有3个子类,不应该走到这
        }
    }

    public void register(FormatterRegistry registry) {
        registry.addFormatterForFieldType(dateClass, this);
    }

    public void setDefaultTimeZone(TimeZone defaultTimeZone) {
        this.defaultTimeZone = defaultTimeZone;
    }

    public String getPattern() {
        return pattern;
    }

    public void setPattern(String pattern) {
        this.pattern = pattern;
    }

    /**
     * 设置兼容的解析格式,如果{@link #pattern}解析失败会尝试用这些格式解析
     * 
     * @param fallbackPatterns
     * @return
     */
    public MyDateFormatter<T> fallbackPatterns(String[] fallbackPatterns) {
        this.fallbackPatterns = fallbackPatterns;
        return this;
    }

    public boolean isEnableContextTimeZone() {
        return enableContextTimeZone;
    }

    /**
     * 启用上下文时区,启用时如果上下文时区与默认时间不同,将解析出的时间转换到默认时区时间
     * 
     * @param enableContextTimeZone
     */
    public void setEnableContextTimeZone(boolean enableContextTimeZone) {
        this.enableContextTimeZone = enableContextTimeZone;
    }

    public MyDateFormatter<T> enableContextTimeZone(boolean enableContextTimeZone) {
        this.enableContextTimeZone = enableContextTimeZone;
        return this;
    }

    public TimeZone getDefaultTimeZone() {
        return defaultTimeZone;
    }

}
